package edu.kit.csl.pisa.training;

/*
This file is part of the PISA Alignment Tool.

Copyright (C) 2013
Karlsruhe Institute of Technology
Cognitive Systems Lab (CSL)
Felix Stahlberg

PISA is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

PISA is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with PISA. If not, see <http://www.gnu.org/licenses/>.
*/

import edu.kit.csl.pisa.datatypes.BadParameterValueException;
import edu.kit.csl.pisa.datatypes.Corpus;
import edu.kit.csl.pisa.io.Logger;
import edu.kit.csl.pisa.models.AlignmentModel;
import edu.kit.csl.pisa.ui.Configuration;

/**
 * This abstract class have to be implemented by all training strategies. At 
 * the moment, only EM training is implemented.
 * 
 * @see EMTrainer
 */
public abstract class Trainer {

	/**
	 * The alignment model to train. This is set in the constructor and cannot
	 * changed afterwards.
	 */
	protected AlignmentModel model;
	
	/* (non-Javadoc)
	 * Lower-level strategies.
	 */
	protected MaximizationStrategy maxStrategy;
	protected SuccessorStrategy succStrategy;
	
	/**
	 * Sole constructor. Initializes the internal strategies according to which
	 * model should be trained.
	 * 
	 * @param model model which should be trained
	 * @throws BadParameterValueException if one of the configured strategies
	 * 			are unknown.
	 */
	public Trainer(AlignmentModel model) throws BadParameterValueException {
		this.model = model;
		initMaximationStrategy();
		initSuccessorStrategy();
	}
	
	/**
	 * Starts the training procedure. The parameter spec. This method uses the
	 * template method iterate() (GoF).
	 * 
	 * @param corpus the training data (also contains the alignments)
	 * @see #iterate(Corpus)
	 */
	public void train(Corpus corpus) {
		initializeTraining(corpus);
		Logger log = Logger.getSingleton();
		int nIteration = model.getConfigInteger("Iterations");
		int corpusDumpFreq = model.getConfigInteger("CorpusDumpFrequency");
		int modelDumpFreq = model.getConfigInteger("ModelDumpFrequency");
		String outputPath = Configuration.getSingleton()
				.getString("outputPrefix");
		log.notice("Perform " + nIteration + " training iterations.");
		// Use IdentityMaxStrat in first iteration
		MaximizationStrategy origMaxStrat = maxStrategy;
		maxStrategy = new IdentityMaximizationStrategy(model);
		for (int i = 1; i <= nIteration; i++) {
			iterate(corpus);
			log.notice("Iteration " + i + " done.");
			if (i % corpusDumpFreq == 0) {
				String file = outputPath + "." + model.getName() + ".A." + i;
				log.notice("Dump corpus to " + file);
				corpus.dumpToFilesystem(file);
			}
			if (i % modelDumpFreq == 0) {
				log.notice("Dump model parameters.");
				model.dumpToFilesystem(
						outputPath + "." + model.getName() + ".",
						"." + i);
			}
			maxStrategy = origMaxStrat;
		}
	}
	
	/**
	 * This is a template method(GoF) used in train(Corpus). It is called
	 * before the iterations are executed.
	 * 
	 * @param corpus the training data (also contains the alignments)
	 * @see #train(Corpus) 
	 */
	public void initializeTraining(Corpus corpus) {
		return;
	}
	
	/**
	 * This template method (GoF) contains the training strategy. It is called
	 * by train() according to the configuration parameter *iterations. The 
	 * implementing subclass should do one training step for enhancing the
	 * model and/or the alignment set.
	 * 
	 * @param corpus the training data (also contains the alignments)
	 * @see #train(Corpus)
	 */
	protected abstract void iterate(Corpus corpus);
	

	/* (non-Javadoc)
	 * Set succStrategy according model settings.
	 * 
	 * @see Configuration
	 */
	private void initSuccessorStrategy() throws BadParameterValueException {
		String name = model.getConfigString("SuccStrategy");
		if (name.equals("id")) {
			succStrategy = new IdentitySuccessorStrategy(model);
		} else if (name.equals("viterbi")) {
			succStrategy = new ViterbiSuccessorStrategy(model);
		} else if (name.equals("nBest")) {
			succStrategy = new NBestSuccessorStrategy(model);
		} else if (name.equals("random")) {
			succStrategy = new RandomSuccessorStrategy(model);
		} else if (name.equals("no")) {
			succStrategy = new NoSuccessorStrategy(model);
		} else {
			throw new BadParameterValueException("Successor method unknown.");
		}
	}

	/* (non-Javadoc)
	 * Set maxStrategy according model settings.
	 * 
	 * @see Configuration
	 */
	private void initMaximationStrategy() throws BadParameterValueException {
		String name = model.getConfigString("MaxStrategy");
		try {
			if (name.equals("eva2")) {
				maxStrategy = new Eva2MaximizationStrategy(model);
			} else if (name.equals("id")) {
				maxStrategy = new IdentityMaximizationStrategy(model);
			} else {
				throw new BadParameterValueException(
						"Maximization method unknown.");
			}
		} catch (UnsupportedOperationException e) {
			throw new BadParameterValueException(
					"Maximization method is not implemented yet.");
		}
	}
}
